package risoe.syslab.fhnew.controller;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import risoe.syslab.comm.shared.FunctionNotSupportedException;
import risoe.syslab.comm.typebased.interfaces.FlexHouse;
import risoe.syslab.config.CommonDeviceConfig;
import risoe.syslab.fhnew.common.SystemConstants.DeviceType;
import risoe.syslab.fhnew.config.ActuatorConfig;
import risoe.syslab.fhnew.config.BuildingConfig;
import risoe.syslab.fhnew.config.RoomConfig;
import risoe.syslab.fhnew.config.SensorConfig;
import risoe.syslab.hw.pollserver.flexhouse.FlexHouseInternalServerAPI;
import risoe.syslab.hw.pollserver.flexhouse.FlexHouseRoom;
import risoe.syslab.model.CompositeMeasurement;
import risoe.syslab.model.flexhouse.AbstractActuator;
import risoe.syslab.model.flexhouse.ActuatorGroup;
import risoe.syslab.model.flexhouse.EndpointDescription;
import risoe.syslab.model.flexhouse.FlexHouseState;
import risoe.syslab.model.flexhouse.FlexhouseDeviceType;
import risoe.syslab.model.flexhouse.SensorGroup;
import risoe.syslab.model.flexhouse.SimpleActuator;
import risoe.syslab.model.flexhouse.SimpleSensor;
import risoe.syslab.model.iec61850.DELV;
import risoe.syslab.model.iec61850.GPSL;
import risoe.syslab.model.iec61850.HLTH;
import risoe.syslab.model.iec61850.LNPL;
import risoe.syslab.model.iec61850.PNPL;

/**
 * Temporary class for communication using the old RMI interface.
 * @author M. Svendsen
 */
public class FlexHouseInterface implements FlexHouse, Remote {
	public static final int SYSLAB_TIMEOUT_MINUTES = 5;

	private ControllerManager mControllerManager;
	private BuildingConfig mBuildingConfig;

	private FlexHouseInternalServerAPI rmiNode08Server = null;

	public static final Map<DeviceType, FlexhouseDeviceType> typeMap;
	static{
		HashMap<DeviceType, FlexhouseDeviceType> map = new HashMap<DeviceType, FlexhouseDeviceType>();
		map.put(DeviceType.AnalogMeasurement, FlexhouseDeviceType.Unknown); //FIXME THIS IS NOT THE RIGHT TYPE!
		map.put(DeviceType.TemperatureControlledAirCon, FlexhouseDeviceType.AirConditionActuator);
		map.put(DeviceType.Door, FlexhouseDeviceType.DoorSensor);
		map.put(DeviceType.Temperature, FlexhouseDeviceType.ExtraTemperatureSensor);
		map.put(DeviceType.PowerControlledHeater, FlexhouseDeviceType.HeaterActuator);
		map.put(DeviceType.Brightness, FlexhouseDeviceType.IndoorLightSensor);
		map.put(DeviceType.Temperature, FlexhouseDeviceType.IndoorTemperatureSensor);
		map.put(DeviceType.DimmableLight, FlexhouseDeviceType.LightActuator);
		//map.put(DeviceType.Insolation, FlexhouseDeviceType.LightIntensity);
		map.put(DeviceType.Motion, FlexhouseDeviceType.MotionSensor);
		//map.put(DeviceType.Temperature, FlexhouseDeviceType.OutdoorTemperatureSensor);
		map.put(DeviceType.Electrical, FlexhouseDeviceType.PanelInstrument);
		//map.put(DeviceType.AnalogMeasurement, FlexhouseDeviceType.RefrigeratorActuator);
		map.put(DeviceType.Insolation, FlexhouseDeviceType.SolarRadiation);
		map.put(DeviceType.Switch, FlexhouseDeviceType.Switch);
		map.put(DeviceType.Waterboiler, FlexhouseDeviceType.WaterHeaterActuator);
		map.put(DeviceType.WindDirection, FlexhouseDeviceType.WindDirection);
		map.put(DeviceType.WindSpeed, FlexhouseDeviceType.WindSpeed);

		typeMap = Collections.unmodifiableMap(map);
	}

	public static final String HSMM_IP = "syslab-08";
	public static final int HSMM_PORT = 1997;
	public static final String HSMM_NAME = "FlexHouseInternalServerAPI";

	public FlexHouseInterface(ControllerManager manager, BuildingConfig config) {
		mControllerManager = manager;
		mBuildingConfig = config;

		new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					if (rmiNode08Server != null) {
						try {
							System.out.print(".");
							rmiNode08Server.receiveHouseState(getFlexhouseState());
						} catch (RemoteException e) {
							//e.printStackTrace();
							rmiNode08Server = null;
							System.out.println("Disconnected from node 08");
						}
					}

					if (rmiNode08Server == null) {
						// Attempt reconnect
						System.out.println("Connecting to node 08");
						rmiNode08Server = connectToNode();
					}					
				}
			}
		}).start();
	}

	private FlexHouseInternalServerAPI connectToNode() {
		try {
			Registry module = LocateRegistry.getRegistry(HSMM_IP, HSMM_PORT);
			if (module != null) {
				if (module.lookup(HSMM_NAME) != null)
					return (FlexHouseInternalServerAPI) module.lookup(HSMM_NAME);
			}
		} catch (Exception e) {
			System.out.println("Error connecting to syslab-08");
			//e.printStackTrace();
		}

		return null;
	}

	@Override
	public CompositeMeasurement getActivePower() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Psum");
	}

	@Override
	public CompositeMeasurement getActivePowerPhaseA() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_P1");
	}

	@Override
	public CompositeMeasurement getActivePowerPhaseB() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_P2");
	}

	@Override
	public CompositeMeasurement getActivePowerPhaseC() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_P3");
	}

	@Override
	public CompositeMeasurement getReactivePower() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Qsum");
	}

	@Override
	public CompositeMeasurement getReactivePowerPhaseA() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Q1");
	}

	@Override
	public CompositeMeasurement getReactivePowerPhaseB() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Q2");
	}

	@Override
	public CompositeMeasurement getReactivePowerPhaseC() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Q3");
	}

	@Override
	public CompositeMeasurement getGridCosPhi()	throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_PFavg");
	}

	@Override
	public CompositeMeasurement getGridFrequency() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_f");
	}

	@Override
	public DELV getGridVoltages() throws RemoteException {
		return new DELV(mControllerManager.getSensorValueInternal("Grid_Volt12"), mControllerManager.getSensorValueInternal("Grid_Volt23"), mControllerManager.getSensorValueInternal("Grid_Volt31"));
	}

	@Override
	public CompositeMeasurement getActiveEnergyExport() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Pexp");
	}

	@Override
	public CompositeMeasurement getActiveEnergyImport() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Pimp");
	}

	@Override
	public CompositeMeasurement getReactiveEnergyExport() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Qexp");
	}

	@Override
	public CompositeMeasurement getReactiveEnergyImport() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Grid_Qimp");
	}

	@Override
	public int getNumberOfRooms() throws RemoteException {
		return mBuildingConfig.getRooms().size();
	}

	@Override
	public int getNumTriggerSignals() throws RemoteException {
		return 0;
	}

	@Override
	public CompositeMeasurement getTriggerSignal(int trigNum) throws RemoteException {
		return new CompositeMeasurement(0.0);
	}

	@Override
	public CompositeMeasurement getOutsideTemperature() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Outside.Temperature_probe");
	}

	@Override
	public CompositeMeasurement getOutsideWindDirection() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Outside.Windvane");
	}

	@Override
	public CompositeMeasurement getOutsideWindspeed() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Outside.Anemometer");
	}

	@Override
	public CompositeMeasurement getSolarIrradiation() throws RemoteException {
		return mControllerManager.getSensorValueInternal("Outside.Pyranometer_1");
	}

	@Override
	public CommonDeviceConfig getNodeConfiguration() throws RemoteException {
		// TODO: Implement?
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public String getHouseName() throws RemoteException {
		return mBuildingConfig.getBuildingName();
	}

	@Override
	public LNPL getFlexhouseLogicalNameplate() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public PNPL getFlexhousePhysicalNameplate() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public HLTH getFlexhouseHealth() throws RemoteException {
		return new HLTH(HLTH.HEALTH_OK);
	}

	@Override
	public GPSL getGPSLocation() throws RemoteException {
		return new GPSL(mBuildingConfig.getLongitude(), mBuildingConfig.getLatitude(), mBuildingConfig.getAltitude());
	}

	@Override
	public double getRatedP() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public double getRatedQ() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public double getRatedS() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public double getRatedU() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public double getRatedf() throws RemoteException {
		throw new FunctionNotSupportedException("Function Not Implemented");
	}

	@Override
	public SensorGroup getRoomSensorState(String roomName) throws RemoteException {
		SensorGroup group = new SensorGroup();
		for (EndpointDescription sensor : mBuildingConfig.getSensorDescriptions()){
			if (sensor.getRoom().equals(roomName)){
				CompositeMeasurement val = mControllerManager.getSensorValue(sensor.getName());
				FlexhouseDeviceType type = typeMap.get(sensor.getType());
				if (type == null) type = FlexhouseDeviceType.Unknown;
				group.addSensor(new SimpleSensor(sensor.getName(), type, val));
			}
		}

		return group;
	}

	@Override
	public ActuatorGroup getRoomActuatorState(String roomName) throws RemoteException {
		ActuatorGroup group = new ActuatorGroup();
		for (EndpointDescription actuator : mBuildingConfig.getActuatorDescriptions()){
			if (actuator.getRoom().equals(roomName)){
				CompositeMeasurement val = mControllerManager.getSensorValue(actuator.getName());
				FlexhouseDeviceType type = typeMap.get(actuator.getType());
				if (type == null) type = FlexhouseDeviceType.Unknown;
				group.addActuator(new SimpleActuator(actuator.getName(), type, val));
			}
		}

		return group;
	}

	@Override
	public String getRoomName(int roomNo) throws RemoteException {
		return mBuildingConfig.getRooms().get(roomNo).getRoomName();
	}

	@Override
	public FlexHouseState getFlexhouseState() throws RemoteException {
		FlexHouseState state = new FlexHouseState();

		// Add rooms
		for (RoomConfig config : mBuildingConfig.getRooms()) {
			FlexHouseRoom room = new FlexHouseRoom(config.getRoomName());

			// Add Sensors
			for (SensorConfig sensor : config.getSensors()) {
				CompositeMeasurement val = mControllerManager.getSensorValue(sensor.getName());
				FlexhouseDeviceType type = typeMap.get(sensor.getType());
				if (type == null) type = FlexhouseDeviceType.Unknown;

				room.addSensor(new SimpleSensor(sensor.getName(), type, val));
			}

			// Add Actuators
			for (ActuatorConfig actuator : config.getActuators()) {
				CompositeMeasurement val = mControllerManager.getActuatorValue(actuator.getName());
				FlexhouseDeviceType type = typeMap.get(actuator.getType());
				if (type == null) type = FlexhouseDeviceType.Unknown;

				room.addActuator(new SimpleActuator(actuator.getName(), type, val));
			}

			state.addRoom(room);
		}

		// TODO Add the environment
		//state.addEnvironment(new FlexHouseEnvironment(type, name, address, channel, value))
		
		// Hack - Add outside and electrical measurements to room 0
		for (SensorConfig sensor : mBuildingConfig.getBuildingLevelSensors()) {
			CompositeMeasurement val = mControllerManager.getSensorValue(sensor.getName());
			FlexhouseDeviceType type = typeMap.get(sensor.getType());
			if (type == null) type = FlexhouseDeviceType.Unknown;

			state.getRooms()[0].addSensor(new SimpleSensor(sensor.getName(), type, val));
		}

		return state;
	}

	@Override
	public void setActuatorState(AbstractActuator a, String newState) throws RemoteException {
		CompositeMeasurement value = newState.equals("1.0")? new CompositeMeasurement(1.0): new CompositeMeasurement(0.0);
		mControllerManager.setActuatorSyslab(a.getName(), value);
	}
}
